/*
 * Craete by ZhiHui Liu
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


typedef struct {
    ngx_uint_t                  received;
    ngx_uint_t                  sent;
    ngx_uint_t                  requests;
    ngx_uint_t                  connections;

    ngx_uint_t                  status_1xx;
    ngx_uint_t                  status_2xx;
    ngx_uint_t                  status_3xx;
    ngx_uint_t                  status_4xx;
    ngx_uint_t                  status_5xx;
} ngx_http_traffic_record_t;


typedef struct {
    ngx_msec_t                  start;
    ngx_msec_t                  end;

    ngx_uint_t                  last_received;
    ngx_uint_t                  last_sent;
    ngx_uint_t                  last_requests;
    ngx_uint_t                  last_connection;

    ngx_uint_t                  received;
    ngx_uint_t                  sent;
    ngx_uint_t                  requests;
    ngx_uint_t                  connections;
} ngx_http_traffic_peroid_t;

typedef struct ngx_http_traffic_hash_elt_s ngx_http_traffic_hash_elt_t;
struct ngx_http_traffic_hash_elt_s {
    ngx_http_traffic_record_t   record;
    uint32_t                    srv_len;
    uint32_t                    loc_len;
    uint32_t                    key;
    uint32_t                    size;

    ngx_http_traffic_hash_elt_t *next;
    u_char                      name[0];
};


typedef struct {
    ngx_uint_t                  used;
    ngx_uint_t                  size;
    ngx_http_traffic_hash_elt_t **buckets;

    ngx_http_traffic_hash_elt_t *free;
    ngx_slab_pool_t             *shpool;
} ngx_http_traffic_hash_t;


typedef struct {
    ngx_uint_t                  arranged;
    ngx_uint_t                  size;
    ngx_http_traffic_hash_t     hash;
    ngx_msec_t                  last_update;
    ngx_http_traffic_peroid_t   peroid;
} ngx_http_traffic_shm_ctx_t;


typedef struct {
    ngx_slab_pool_t             *shpool;
    ngx_shmtx_t                 *mutex;

    ngx_msec_t                  *last_update;
    ngx_uint_t                  *arranged;
    ngx_http_traffic_hash_t     *hash;
    ngx_pool_t                  *pool;
    ngx_http_traffic_peroid_t   *peroid;

    ngx_array_t                 servers;
    ngx_array_t                 upstreams;
} ngx_http_traffic_ctx_t;


typedef struct {
    ngx_str_t                   *name;
    ngx_http_traffic_record_t   record;
    ngx_http_traffic_hash_elt_t *elt;
    ngx_uint_t                  last_send;
    ngx_uint_t                  last_received;
    ngx_uint_t                  last_requests;
} ngx_http_traffic_loc_conf_t;


typedef struct {
    ngx_str_t                   *name;
    ngx_http_traffic_record_t   record;

    ngx_http_traffic_hash_elt_t *elt;

    /* ngx_http_traffic_loc_conf_t * */
    ngx_array_t                 locations;

    ngx_uint_t                  last_send;
    ngx_uint_t                  last_received;
    ngx_uint_t                  last_requests;
} ngx_http_traffic_srv_conf_t;


typedef struct {
    ngx_event_t                 timer;
    ngx_msec_t                  interval;   /* default 3000 */
    ngx_uint_t                  last_send;
    ngx_uint_t                  last_received;
} ngx_http_traffic_main_conf_t;


static ngx_atomic_t             *ngx_stat_1xx;
static ngx_atomic_t             *ngx_stat_2xx;
static ngx_atomic_t             *ngx_stat_3xx;
static ngx_atomic_t             *ngx_stat_4xx;
static ngx_atomic_t             *ngx_stat_5xx;
static ngx_atomic_t             *ngx_stat_received;
static ngx_atomic_t             *ngx_stat_sent;


static ngx_http_traffic_ctx_t   *ngx_http_traffic_ctx = NULL;
static ngx_str_t                ngx_http_traffic_shm_name =
        ngx_string("ngx_http_traffic_module");

static char *ngx_http_traffic_status(ngx_conf_t *cf,
    ngx_command_t *command, void *conf);
static char *ngx_http_traffic_interface(ngx_conf_t *cf,
    ngx_command_t *command, void *conf);
static char *ngx_http_traffic_merge_loc_conf(ngx_conf_t *cf,
    void *prev, void *conf);

static void *ngx_http_traffic_create_main_conf(ngx_conf_t *cf);
static void *ngx_http_traffic_create_srv_conf(ngx_conf_t *cf);
static void *ngx_http_traffic_create_loc_conf(ngx_conf_t *cf);
static ngx_int_t ngx_http_traffic_postconfiguration(ngx_conf_t *cf);

static ngx_int_t ngx_http_traffic_shm_zone_init(ngx_shm_zone_t *shm_zone, void *data);
static ngx_int_t ngx_http_traffic_init_process(ngx_cycle_t *cycle);

static void ngx_http_traffic_timer_handler(ngx_event_t *ev);
static ngx_int_t ngx_http_traffic_handler(ngx_http_request_t *r);

static ngx_http_traffic_hash_elt_t *
ngx_http_traffic_hash_find(ngx_http_traffic_hash_t *hash, u_char *key, size_t len);

static ngx_int_t
ngx_http_traffic_hash_insert(ngx_http_traffic_hash_t *hash,
    ngx_http_traffic_hash_elt_t *elt);

static ngx_int_t ngx_http_traffic_hash_delete(ngx_http_traffic_hash_t *hash,
    ngx_http_traffic_hash_elt_t *elt);

static ngx_http_traffic_hash_elt_t *
ngx_http_traffic_hash_get_free_elt(ngx_http_traffic_hash_t *hash, size_t size);

static ngx_int_t ngx_http_traffic_status_handler(ngx_http_request_t *r);


static ngx_command_t ngx_http_traffic_commands[] = {
        {
                ngx_string("traffic_status"),
                NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1 | NGX_CONF_NOARGS,
                ngx_http_traffic_status,
                NGX_HTTP_MAIN_CONF_OFFSET,
                0,
                NULL
        },

        {
                ngx_string("traffic_interface"),
                NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
                ngx_http_traffic_interface,
                NGX_HTTP_LOC_CONF_OFFSET,
                0,
                NULL
        },

        ngx_null_command
};


static ngx_http_module_t ngx_http_traffic_module_ctx = {
        NULL,                                   /* preconfiguration */
        ngx_http_traffic_postconfiguration,     /* postconfiguration */

        ngx_http_traffic_create_main_conf,      /* create main configuration */
        NULL,                                   /* init main configuration */

        ngx_http_traffic_create_srv_conf,       /* create server configuration */
        NULL,                                   /* merge server configuration */

        ngx_http_traffic_create_loc_conf,       /* create location configuration */
        ngx_http_traffic_merge_loc_conf         /* merge location configuration */
};


ngx_module_t ngx_http_traffic_module = {
        NGX_MODULE_V1,
        &ngx_http_traffic_module_ctx,           /* module context */
        ngx_http_traffic_commands,              /* module directives */
        NGX_HTTP_MODULE,                        /* module type */
        NULL,                                   /* init master */
        NULL,                                   /* init module */
        ngx_http_traffic_init_process,          /* init process */
        NULL,                                   /* init thread */
        NULL,                                   /* exit thread */
        NULL,                                   /* exit process */
        NULL,                                   /* exit master */
        NGX_MODULE_V1_PADDING
};



static void *
ngx_http_traffic_create_main_conf(ngx_conf_t *cf)
{
    ngx_http_traffic_main_conf_t        *tmcf;

    tmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_traffic_main_conf_t));
    if(tmcf == NULL) {
        return NULL;
    }

    tmcf->interval = 1000;
    tmcf->timer.handler = ngx_http_traffic_timer_handler;
    tmcf->timer.log = cf->log;

    return tmcf;
}


static void *
ngx_http_traffic_create_srv_conf(ngx_conf_t *cf)
{
    ngx_http_traffic_srv_conf_t         *tscf;

    tscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_traffic_srv_conf_t));
    if(tscf == NULL) {
        return NULL;
    }

    return tscf;
}


static void *
ngx_http_traffic_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_traffic_loc_conf_t         *tlcf;

    tlcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_traffic_loc_conf_t));
    if(tlcf == NULL) {
        return NULL;
    }

    return tlcf;
}


static char *
ngx_http_traffic_merge_loc_conf(ngx_conf_t *cf, void *prev, void *conf)
{
    ngx_int_t                           rv;
    ngx_http_core_loc_conf_t            *clcf;
    ngx_http_traffic_srv_conf_t         *tscf;
    ngx_http_traffic_loc_conf_t         *tlcf, **tlcfp;

    tscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_traffic_module);
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    tlcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_traffic_module);

    if(clcf->name.len == 0) {
        return NGX_OK;
    }

    if(tscf->locations.elts == NULL) {
        rv = ngx_array_init(&tscf->locations, cf->pool, 16,
                sizeof(ngx_http_traffic_loc_conf_t *));

        if(rv != NGX_OK) {
            return NGX_CONF_ERROR;
        }
    }

    tlcfp = ngx_array_push(&tscf->locations);
    if(tlcfp == NULL) {
        return NGX_CONF_ERROR;
    }

    tlcf->name = &clcf->name;
    *tlcfp = tlcf;

    return NGX_CONF_OK;
}


static char *
ngx_http_traffic_status(ngx_conf_t *cf, ngx_command_t *command, void *conf)
{
    ngx_int_t                           n;
    ngx_str_t                           *value;
    ngx_shm_zone_t                      *shm_zone;
    ngx_http_traffic_main_conf_t        *tmcf;

    tmcf = ngx_http_conf_get_module_main_conf(cf,
                                              ngx_http_traffic_module);

    if(cf->args->nelts == 1) {
        /* no args */
        tmcf->interval = 3000;
        goto set;
    }

    value = cf->args->elts;
    value += 1;

    if(value->len < sizeof("interval=") - 1) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                            "invalid param for \"traffic_status\"");

        return NGX_CONF_ERROR;
    }

    n = ngx_atoi(value->data + 9, value->len - 9);

    if(n < 1000 || n == NGX_ERROR) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                            "invalid param for \"traffic_status interval\"");

        return NGX_CONF_ERROR;
    }

    tmcf->interval = (ngx_uint_t) n;

set:
    shm_zone = ngx_shared_memory_add(cf,
                                     &ngx_http_traffic_shm_name,
                                     4 * 1024 * 1024,
                                     &ngx_http_traffic_module);
    if(shm_zone == NULL) {
        return NGX_CONF_ERROR;
    }

    ngx_http_traffic_ctx = ngx_pcalloc(cf->pool,
                                       sizeof(ngx_http_traffic_ctx_t));
    if(ngx_http_traffic_ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    shm_zone->init = ngx_http_traffic_shm_zone_init;
    shm_zone->data = ngx_http_traffic_ctx;

    return NGX_CONF_OK;
}


static char *
ngx_http_traffic_interface(ngx_conf_t *cf, ngx_command_t *command, void *conf)
{
    ngx_http_core_loc_conf_t            *clcf;

    if(ngx_http_traffic_ctx == NULL) {
        ngx_log_error_core(NGX_LOG_EMERG, cf->log, 0,
                           "ngx_http_traffic_module is not enabled");

        return NGX_CONF_ERROR;
    }

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_traffic_status_handler;

    return NGX_CONF_OK;
}


static ngx_int_t
ngx_http_traffic_postconfiguration(ngx_conf_t *cf)
{
    ngx_uint_t                          i;
    ngx_http_handler_pt                 *h;
    ngx_http_core_main_conf_t           *cmcf;
    ngx_http_core_srv_conf_t            *cscf, **cscfp;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    if(ngx_http_traffic_ctx == NULL) {
        /* module not enable */
        return NGX_OK;
    }

    cscfp = cmcf->servers.elts;
    for(i = 0; i < cmcf->servers.nelts; ++i) {
        cscf = cscfp[i];

        if(cscf->server_names.nelts != 1) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                "every server should have a name");

            return NGX_ERROR;
        }
    }

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers);
    if(h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_traffic_handler;

    return NGX_OK;
}


static ngx_int_t
ngx_http_traffic_handler(ngx_http_request_t *r)
{
    ngx_connection_t                    *conn;
    ngx_http_traffic_srv_conf_t         *tscf;
    ngx_http_traffic_loc_conf_t         *tlcf;
    ngx_http_upstream_srv_conf_t        *uscf;

#if (NGX_DEBUG)
    u_char                              buf[1024];
    ngx_str_t                           ip;

    ip.data = (u_char *) buf;
    ip.len = ngx_inet_ntop(r->connection->sockaddr->sa_family, r->connection->sockaddr, buf, 1024);
#endif

    tscf = ngx_http_get_module_srv_conf(r, ngx_http_traffic_module);
    tlcf = ngx_http_get_module_loc_conf(r, ngx_http_traffic_module);
    conn = r->connection;

    tscf->record.received += conn->received;
    tscf->record.sent += conn->sent;
    tscf->record.requests += 1;

    tlcf->record.received += conn->received;
    tlcf->record.sent += conn->sent;
    tlcf->record.requests += 1;

    ngx_atomic_fetch_add(ngx_stat_received, conn->received);
    ngx_atomic_fetch_add(ngx_stat_sent, conn->sent);

    if(r->headers_out.status < 200) {
        ngx_atomic_fetch_add(ngx_stat_1xx, 1);
        tscf->record.status_1xx += 1;
    } else if(r->headers_out.status < 300) {
        ngx_atomic_fetch_add(ngx_stat_2xx, 1);
        tscf->record.status_2xx += 1;
    } else if(r->headers_out.status < 400) {
        ngx_atomic_fetch_add(ngx_stat_3xx, 1);
        tscf->record.status_3xx += 1;
    } else if(r->headers_out.status < 500) {
        ngx_atomic_fetch_add(ngx_stat_4xx, 1);
        tscf->record.status_4xx += 1;
    } else {
        ngx_atomic_fetch_add(ngx_stat_5xx, 1);
        tscf->record.status_5xx += 1;
    }

    /* upstream */
    if(r->upstream == NULL) {
        goto done;
    }

    uscf = r->upstream->conf->upstream;
    tscf = uscf->srv_conf[ngx_http_traffic_module.ctx_index];

    tlcf = ((ngx_http_traffic_loc_conf_t *)tscf->locations.elts) + r->upstream->peer.peer_index;

    tscf->record.received += conn->received;
    tscf->record.sent += conn->sent;
    tscf->record.requests += 1;

    tlcf->record.sent += r->connection->sent;
    tlcf->record.received += r->connection->received;
    tlcf->record.requests += 1;

    if(r->headers_out.status < 200) {
        tscf->record.status_1xx += 1;
        tlcf->record.status_1xx += 1;
    } else if(r->headers_out.status < 300) {
        tscf->record.status_2xx += 1;
        tlcf->record.status_2xx += 1;
    } else if(r->headers_out.status < 400) {
        tscf->record.status_3xx += 1;
        tlcf->record.status_3xx += 1;
    } else if(r->headers_out.status < 500) {
        tscf->record.status_4xx += 1;
        tlcf->record.status_4xx += 1;
    } else {
        tscf->record.status_5xx += 1;
        tlcf->record.status_5xx += 1;
    }

done:
    ngx_log_debug6(NGX_LOG_DEBUG, r->connection->log, 0,
                   "client %V, %V, %V, %ul, received: %ul, sent: %ul",
                   &ip, &r->method_name, &r->uri,
                   r->headers_out.status, conn->received, conn->sent);

    return NGX_OK;
}


static ngx_int_t
ngx_http_traffic_shm_zone_init(ngx_shm_zone_t *shm_zone, void *data)
{
    size_t                              size, cl;
    u_char                              *shared;
    ngx_slab_pool_t                     *shpool;
    ngx_http_traffic_ctx_t              *ctx, *octx = data;
    ngx_http_traffic_hash_t             *hash;
    ngx_http_traffic_shm_ctx_t          *shm_ctx;

    ctx = shm_zone->data;
    if(octx) {
        /* nginx reload */
        ngx_log_error_core(NGX_LOG_DEBUG, shm_zone->shm.log, 0,
                      "ngx_http_traffic_module inheriate ctx from old process");

        shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
        ctx->shpool = shpool;
        ctx->hash = octx->hash;
        ctx->mutex = &shpool->mutex;
        ctx->arranged = octx->arranged;
        ctx->last_update = octx->last_update;
        *ctx->arranged = 0;
        ctx->peroid = octx->peroid;

        return NGX_OK;
    }

    /* nginx start */
    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;

    if(shm_zone->shm.exists) {
        shm_ctx = shpool->data;
        ctx->hash = &shm_ctx->hash;
        ctx->arranged = &shm_ctx->arranged;
        *ctx->arranged = 0;
        ctx->mutex = &shpool->mutex;
        ctx->shpool = shpool;
        ctx->last_update = &shm_ctx->last_update;
        ctx->peroid = &shm_ctx->peroid;

        return NGX_OK;
    }

    /* init shm */
    ctx->shpool = shpool;
    ctx->mutex = &shpool->mutex;

    cl = 128;
    size = cl               /* ngx_stat_received */
           + cl             /* ngx_stat_send */
           + cl             /* ngx_stat_received_rate */
           + cl             /* ngx_stat_send_rate */
           + cl             /* ngx_stat_1xx */
           + cl             /* ngx_stat_2xx */
           + cl             /* ngx_stat_3xx */
           + cl             /* ngx_stat_4xx */
           + cl;            /* ngx_stat_5xx */

    shared = ngx_slab_alloc(shpool, size);
    if(shared == NULL) {
        return NGX_ERROR;
    }

    ngx_stat_received = (ngx_atomic_t *) (shared + 0 * cl);
    ngx_stat_sent = (ngx_atomic_t *) (shared + 1 * cl);
    ngx_stat_1xx = (ngx_atomic_t *) (shared + 4 *cl);
    ngx_stat_2xx = (ngx_atomic_t *) (shared + 5 *cl);
    ngx_stat_3xx = (ngx_atomic_t *) (shared + 6 *cl);
    ngx_stat_4xx = (ngx_atomic_t *) (shared + 7 *cl);
    ngx_stat_5xx = (ngx_atomic_t *) (shared + 8 *cl);

    shm_ctx = ngx_slab_alloc(shpool, sizeof(ngx_http_traffic_shm_ctx_t));
    if(shm_ctx == NULL) {
        return NGX_ERROR;
    }

    ngx_memzero(shm_ctx, sizeof(ngx_http_traffic_shm_ctx_t));

    /* init hash */
    hash = &shm_ctx->hash;
    hash->buckets = ngx_slab_alloc(shpool, sizeof(ngx_http_traffic_hash_elt_t *) * 256);
    hash->size = 256;
    hash->used = 0;
    hash->free = NULL;
    hash->shpool = shpool;
    ngx_memzero(hash->buckets, sizeof(ngx_http_traffic_hash_elt_t *) * 256);

    shm_ctx->hash.shpool = shpool;
    shm_ctx->arranged = 0;
    ctx->hash = &shm_ctx->hash;
    ctx->arranged = &shm_ctx->arranged;
    ctx->last_update = &shm_ctx->last_update;
    ctx->peroid = &shm_ctx->peroid;
    ctx->peroid->start = ngx_current_msec - 1000;
    ctx->peroid->end = ngx_current_msec;
    shpool->data = shm_ctx;
    ngx_http_traffic_ctx = ctx;

    return NGX_OK;
}


static ngx_int_t
ngx_http_traffic_link_srv_elt(ngx_http_traffic_srv_conf_t *tscf)
{
    ngx_http_traffic_hash_elt_t         *elt;

    elt = ngx_http_traffic_hash_find(ngx_http_traffic_ctx->hash,
                                     tscf->name->data, tscf->name->len);
    if(elt != NULL) {
        goto set;
    }

    elt = ngx_http_traffic_hash_get_free_elt(ngx_http_traffic_ctx->hash, tscf->name->len);
    if(elt == NULL) {
        return NGX_ERROR;
    }

    ngx_memcpy(elt->name, tscf->name->data, tscf->name->len);
    elt->srv_len = (uint32_t) tscf->name->len;
    ngx_http_traffic_hash_insert(ngx_http_traffic_ctx->hash, elt);

set:
    tscf->elt = elt;

    ngx_log_debug(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                  "ngx_http_traffic_module link \"%V\"", tscf->name);

    return NGX_OK;
}


static ngx_int_t
ngx_http_traffic_link_loc_elt(ngx_http_traffic_srv_conf_t *tscf,
    ngx_http_traffic_loc_conf_t *tlcf)
{
    u_char                              buf[512];
    ngx_uint_t                          len;
    ngx_http_traffic_hash_elt_t         *elt;

    ngx_memcpy(buf, tscf->name->data, tscf->name->len);
    ngx_memcpy(buf + tscf->name->len, tlcf->name->data, tlcf->name->len);

    len = tscf->name->len + tlcf->name->len;

    elt = ngx_http_traffic_hash_find(ngx_http_traffic_ctx->hash, buf, len);
    if(elt != NULL) {
        goto set;
    }

    elt = ngx_http_traffic_hash_get_free_elt(ngx_http_traffic_ctx->hash, len);
    if(elt == NULL) {
        return NGX_ERROR;
    }

    ngx_memcpy(elt->name, buf, len);
    elt->srv_len = (uint32_t) tscf->name->len;
    elt->loc_len = (uint32_t) tlcf->name->len;
    ngx_http_traffic_hash_insert(ngx_http_traffic_ctx->hash, elt);

set:
    tlcf->elt = elt;

    ngx_log_debug(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                  "ngx_http_traffic_module link \"%V\" \"%V\"", tscf->name, tlcf->name);

    return NGX_OK;
}


static ngx_int_t
ngx_http_traffic_unlink_elts()
{
    ngx_uint_t                          i, j, k;
    ngx_http_traffic_hash_t             *hash;
    ngx_http_traffic_hash_elt_t         *elt;
    ngx_http_traffic_srv_conf_t         *tscf, **tscfp;
    ngx_http_traffic_loc_conf_t         *tlcf, **tlcfp;


    hash = ngx_http_traffic_ctx->hash;

    for(i = 0; i < hash->size; ++i) {
    start:
        elt = hash->buckets[i];

        if(elt == NULL) {
            continue;
        }

    next:
        /* servers */
        tscfp = ngx_http_traffic_ctx->servers.elts;
        for(j = 0; j < ngx_http_traffic_ctx->servers.nelts; ++j) {
            tscf = tscfp[j];

            if(elt->srv_len != tscf->name->len) {
                continue;
            }

            if(ngx_strncmp(tscf->name->data, elt->name, elt->srv_len) != 0) {
                continue;
            }

            if(elt->loc_len == 0) {
                goto found;
            }

            tlcfp = tscf->locations.elts;
            for(k = 0; k < tscf->locations.nelts; ++k) {
                tlcf = tlcfp[k];

                if(tlcf->name->len != elt->loc_len) {
                    continue;
                }

                if(ngx_strncmp(elt->name + elt->srv_len, tlcf->name->data, tlcf->name->len) == 0) {
                    goto found;
                }
            }

            /* not found in http server */
        }

        /* upstreams */
        tscfp = ngx_http_traffic_ctx->upstreams.elts;
        for(j = 0; j < ngx_http_traffic_ctx->upstreams.nelts; ++j) {
            tscf = tscfp[j];

            if(elt->srv_len != tscf->name->len) {
                continue;
            }

            if(ngx_strncmp(tscf->name->data, elt->name, elt->srv_len) != 0) {
                continue;
            }

            if(elt->loc_len == 0) {
                goto found;
            }

            /* upstream server */
            for(k = 0; k < tscf->locations.nelts; ++k) {
                tlcf = ((ngx_http_traffic_loc_conf_t *)tscf->locations.elts) + k;

                if(tlcf->name->len != elt->loc_len) {
                    continue;
                }

                if(ngx_strncmp(tlcf->name->data, elt->name + elt->srv_len, elt->loc_len) == 0) {
                    goto found;
                }
            }

            /* not found in upstream */
        }

        /* not found  */
        ngx_log_debug(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                      "ngx_http_traffic_module delete elt \"%s\"", elt->name);

        /* the delete function will destroy the link list */
        ngx_http_traffic_hash_delete(ngx_http_traffic_ctx->hash, elt);
        goto start;

    found:

        if(elt->next != NULL) {
            elt = elt->next;
            goto next;
        }
    }

    return NGX_OK;
}


static ngx_int_t
ngx_http_traffic_init_process(ngx_cycle_t *cycle)
{
    ngx_int_t                           rv;
    ngx_msec_t                          t, delay;
    ngx_uint_t                          i, j;
    ngx_http_server_name_t              *sn;
    ngx_http_traffic_ctx_t              *ctx;
    ngx_http_core_srv_conf_t            *cscf, **cscfp;
    ngx_http_core_main_conf_t           *cmcf;
    ngx_http_upstream_rr_peer_t         *peer;
    ngx_http_traffic_srv_conf_t         *tscf, **ptscf;
    ngx_http_traffic_loc_conf_t         *tlcf, **tlcfp;
    ngx_http_traffic_main_conf_t        *tmcf;
    ngx_http_upstream_rr_peers_t        *peers;
    ngx_http_upstream_srv_conf_t        *uscf, **uscfp;
    ngx_http_upstream_main_conf_t       *umcf;

    ctx = ngx_http_traffic_ctx;

    if(ctx == NULL) {
        /* this module is not enabled */
        return NGX_OK;
    }

    tmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_traffic_module);
    cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);
    umcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_upstream_module);

    rv = ngx_array_init(&ctx->servers, cycle->pool, 16,
            sizeof(ngx_http_traffic_srv_conf_t *));
    if(rv != NGX_OK) {
        return NGX_ERROR;
    }

    rv = ngx_array_init(&ctx->upstreams, cycle->pool, 16,
            sizeof(ngx_http_traffic_srv_conf_t *));
    if(rv != NGX_OK) {
        return NGX_ERROR;
    }

    ngx_shmtx_lock(ctx->mutex);

    /* servers */
    cscfp = cmcf->servers.elts;
    for(i = 0; i < cmcf->servers.nelts; ++i) {
        cscf = cscfp[i];
        tscf = cscf->ctx->srv_conf[ngx_http_traffic_module.ctx_index];
        sn = cscf->server_names.elts;

        tscf->name = &sn->name;

        ptscf = ngx_array_push(&ctx->servers);
        if(ptscf == NULL) {
            goto error;
        }

        *ptscf = tscf;
        if(ngx_http_traffic_link_srv_elt(tscf) != NGX_OK) {
            goto error;
        }

        tlcfp = tscf->locations.elts;
        for(j = 0; j < tscf->locations.nelts; ++j) {
            tlcf = tlcfp[j];

            if(ngx_http_traffic_link_loc_elt(tscf, tlcf) != NGX_OK) {
                goto error;
            }
        }
    }

    /* upstreams */
    uscfp = umcf->upstreams.elts;
    for(i = 0; i < umcf->upstreams.nelts; ++i) {
        uscf = uscfp[i];
        tscf = uscf->srv_conf[ngx_http_traffic_module.ctx_index];

        tscf->name = &uscf->host;

        ptscf = ngx_array_push(&ctx->upstreams);
        if(ptscf == NULL) {
            goto error;
        }

        *ptscf = tscf;
        rv = ngx_array_init(&tscf->locations, cycle->pool, 8,
                            sizeof(ngx_http_traffic_loc_conf_t));
        if(rv != NGX_OK) {
            goto error;
        }

        ngx_memzero(tscf->locations.elts,
                    tscf->locations.nalloc * sizeof(ngx_http_traffic_loc_conf_t));

        if(ngx_http_traffic_link_srv_elt(tscf) != NGX_OK) {
            goto error;
        }

        peers = uscf->peer.data;
        for(j = 0; j < peers->number; ++j) {
            peer = peers->peer + j;

            tlcf = ngx_array_push(&tscf->locations);
            if(tlcf == NULL) {
                goto error;
            }

            tlcf->name = &peer->name;

            if(ngx_http_traffic_link_loc_elt(tscf, tlcf) != NGX_OK) {
                goto error;
            }
        }
    }

    rv = ngx_http_traffic_unlink_elts();
    if(rv != NGX_OK) {
        goto error;
    }


    ngx_shmtx_unlock(ctx->mutex);

    srandom((unsigned int)ngx_pid);
    t = tmcf->interval > 1000 ? tmcf->interval : 1000;
    delay = ngx_random() % t;

    tmcf->timer.handler = ngx_http_traffic_timer_handler;
    tmcf->timer.data = tmcf;
    ngx_event_add_timer(&tmcf->timer, delay);

    ngx_log_debug(NGX_LOG_DEBUG_HTTP, cycle->log, 0,
                  "ngx_http_traffic_module init worker worker success ");

    return NGX_OK;

error:
    ngx_shmtx_unlock(ctx->mutex);

    return NGX_OK;
}


static void
ngx_http_traffic_timer_handler(ngx_event_t *ev)
{
    ngx_uint_t                              i, j;
    ngx_http_traffic_ctx_t                  *ctx;
    ngx_http_traffic_srv_conf_t             *tscf, **tscfp;
    ngx_http_traffic_loc_conf_t             *tlcf, **tlcfp;
    ngx_http_traffic_main_conf_t            *tmcf;

    ctx = ngx_http_traffic_ctx;
    tmcf = ev->data;

    if(ngx_current_msec - ctx->peroid->end >= tmcf->interval) {
        ctx->peroid->start = ctx->peroid->end;
        ctx->peroid->end = ngx_current_msec;
        ctx->peroid->last_connection = ctx->peroid->connections;
        ctx->peroid->last_received = ctx->peroid->received;
        ctx->peroid->last_requests = ctx->peroid->requests;
        ctx->peroid->last_sent = ctx->peroid->sent;
        ctx->peroid->connections = *ngx_stat_accepted;
        ctx->peroid->requests = *ngx_stat_requests;
        ctx->peroid->received = *ngx_stat_received;
        ctx->peroid->sent = *ngx_stat_sent;
    }

    ngx_shmtx_lock(ngx_http_traffic_ctx->mutex);

    /* server */
    tscfp = ctx->servers.elts;
    for(i = 0; i < ctx->servers.nelts; ++i) {
        tscf = tscfp[i];

        tscf->elt->record.received += tscf->record.received;
        tscf->elt->record.requests += tscf->record.requests;
        tscf->elt->record.sent += tscf->record.sent;
        tscf->elt->record.status_1xx += tscf->record.status_1xx;
        tscf->elt->record.status_2xx += tscf->record.status_2xx;
        tscf->elt->record.status_3xx += tscf->record.status_3xx;
        tscf->elt->record.status_4xx += tscf->record.status_4xx;
        tscf->elt->record.status_5xx += tscf->record.status_5xx;

        ngx_memzero(&tscf->record, sizeof(ngx_http_traffic_record_t));

        /* locations */
        tlcfp = tscf->locations.elts;
        for(j = 0; j < tscf->locations.nelts; ++j) {
            tlcf = tlcfp[j];

            tlcf->elt->record.received += tlcf->record.received;
            tlcf->elt->record.requests += tlcf->record.requests;
            tlcf->elt->record.sent += tlcf->record.sent;

            ngx_memzero(&tlcf->record, sizeof(ngx_http_traffic_record_t));
        }
    }

    /* upstreams */
    tscfp = ctx->upstreams.elts;
    for(i = 0; i < ctx->upstreams.nelts; ++i) {
        tscf = tscfp[i];

        tscf->elt->record.received += tscf->record.received;
        tscf->elt->record.sent += tscf->record.sent;
        tscf->elt->record.requests += tscf->record.requests;
        tscf->elt->record.status_1xx += tscf->record.status_1xx;
        tscf->elt->record.status_2xx += tscf->record.status_2xx;
        tscf->elt->record.status_3xx += tscf->record.status_3xx;
        tscf->elt->record.status_4xx += tscf->record.status_4xx;
        tscf->elt->record.status_5xx += tscf->record.status_5xx;

        ngx_memzero(&tscf->record, sizeof(ngx_http_traffic_record_t));

        for(j = 0; j < tscf->locations.nelts; ++j) {
            tlcf = ((ngx_http_traffic_loc_conf_t *) tscf->locations.elts) + j;

            tlcf->elt->record.received += tlcf->record.received;
            tlcf->elt->record.sent += tlcf->record.sent;
            tlcf->elt->record.requests += tlcf->record.requests;
            tlcf->elt->record.status_1xx += tlcf->record.status_1xx;
            tlcf->elt->record.status_2xx += tlcf->record.status_2xx;
            tlcf->elt->record.status_3xx += tlcf->record.status_3xx;
            tlcf->elt->record.status_4xx += tlcf->record.status_4xx;
            tlcf->elt->record.status_5xx += tlcf->record.status_5xx;

            ngx_memzero(&tlcf->record, sizeof(ngx_http_traffic_record_t));
        }
    }

    ngx_shmtx_unlock(ngx_http_traffic_ctx->mutex);

    ngx_log_debug(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                  "ngx_http_traffic_module update success");

    if(ngx_exiting || ngx_terminate || ngx_quit) {
        /* process is exiting */
        return;
    }

    ngx_event_add_timer(ev, tmcf->interval);
}


static ngx_http_traffic_hash_elt_t *
ngx_http_traffic_hash_get_free_elt(ngx_http_traffic_hash_t *hash, size_t size)
{
    ngx_http_traffic_hash_elt_t             *elt, *prev;

    size = ngx_align((size + 1), sizeof(uintptr_t));

    if(hash->free == NULL) {
        goto new_elt;
    }

    if(hash->free->size >= size) {
        elt = hash->free;
        hash->free = elt->next;
    }

    prev = hash->free;
    for(;;) {
        elt = prev->next;
        if(elt->size >= size) {
            prev->next = elt->next;
            goto done;
        }
    }

    /* no free elt's size larger than size */
new_elt:
    elt = ngx_slab_alloc_locked(hash->shpool,
                                size + sizeof(ngx_http_traffic_hash_elt_t));
    if(elt == NULL) {
        return NULL;
    }

    ngx_log_debug(NGX_LOG_INFO, ngx_cycle->log, 0,
                    "alloc an elt size: %ul", size);

done:
    ngx_memzero(elt, sizeof(ngx_http_traffic_hash_elt_t) + size);
    elt->size = (uint32_t) size;
    elt->next = NULL;

    return elt;
}


static ngx_http_traffic_hash_elt_t *
ngx_http_traffic_hash_find(ngx_http_traffic_hash_t *hash, u_char *key, size_t len)
{
    uint32_t                                hv;
    ngx_uint_t                              index;
    ngx_http_traffic_hash_elt_t             *elt;

    hv = ngx_murmur_hash2(key, len);
    index = hv & (hash->size - 1);

    elt = hash->buckets[index];
    if(elt == NULL) {
        return NULL;
    }

    for(;elt != NULL; elt = elt->next) {

        if(elt->key != hv) {
            continue;
        }

        if(ngx_strncmp(key, elt->name, len) == 0) {
            return elt;
        }
    }

    return NULL;
}


static ngx_int_t
ngx_http_traffic_hash_insert(ngx_http_traffic_hash_t *hash,
    ngx_http_traffic_hash_elt_t *elt)
{
    uint32_t                                hv;
    ngx_uint_t                              index;

    hv = ngx_murmur_hash2(elt->name, elt->srv_len + elt->loc_len);
    index = hv & (hash->size - 1);

    elt->key = hv;
    elt->next = hash->buckets[index];
    hash->buckets[index] = elt;

    return NGX_OK;
}


static ngx_int_t
ngx_http_traffic_hash_delete(ngx_http_traffic_hash_t *hash,
    ngx_http_traffic_hash_elt_t *elt)
{
    ngx_uint_t                              index;
    ngx_http_traffic_hash_elt_t             *prev, *tmp;

    if(elt->key == 0) {
        /* already delete by other worker */
        return NGX_OK;
    }

    index = elt->key & (hash->size - 1);
    prev = hash->buckets[index];

    if(prev == NULL) {
        return NGX_ERROR;
    }

    elt->key = 0;
    if(prev == elt) {
        hash->buckets[index] = prev->next;
        return NGX_OK;
    }

    for(; prev != NULL;) {
        tmp = prev->next;
        if(tmp == elt) {
            prev->next = tmp->next;
            tmp->next = hash->free;
            hash->free = tmp;

            ngx_log_error_core(NGX_LOG_DEBUG, ngx_cycle->log, 0,
                           "delete key \"%s\" success", elt->name);

            return NGX_OK;
        }

        prev = tmp;
    }

    return NGX_OK;
}


static ngx_buf_t *
ngx_http_traffic_get_buf(ngx_pool_t *pool, ngx_chain_t **chain)
{
    ngx_buf_t                               *buf;
    ngx_chain_t                             *cl;

    buf = (*chain)->buf;
    if(buf->end - buf->last > 128) {
        return buf;
    }

    cl = ngx_alloc_chain_link(pool);
    if(cl == NULL) {
        return NULL;
    }

    buf = ngx_create_temp_buf(pool, 4096);
    if(buf == NULL) {
        return NULL;
    }

    (*chain)->next = cl;
    cl->buf = buf;
    cl->next = NULL;

    return buf;
}


static ngx_int_t
ngx_http_traffic_status_handler(ngx_http_request_t *r)
{
    ngx_buf_t                               *buf = NULL;
    ngx_int_t                               rc;
    ngx_msec_t                              tf;
    ngx_uint_t                              i, j, sr, ss, s1, s2, s3, s4, s5;
    ngx_uint_t                              reqs, qps, rt, ac, in_bw, out_bw;
    ngx_uint_t                              wr, rd, wt;
    ngx_chain_t                             out, *chain;
    ngx_http_traffic_ctx_t                  *ctx;
    ngx_http_traffic_srv_conf_t             *tscf, **tscfp;
    ngx_http_traffic_loc_conf_t             *tlcf, **tlcfp;
    ngx_http_upstream_main_conf_t           *umcf;
    ngx_http_upstream_srv_conf_t            *uscf, **uscfp;

    ctx = ngx_http_traffic_ctx;
    chain = &out;
    umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);

    buf = ngx_create_temp_buf(r->pool, 4 * 1024);
    if(buf == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    out.buf = buf;
    out.next = NULL;

    tf = ctx->peroid->end - ctx->peroid->start;


    sr = *ngx_stat_received;
    ss = *ngx_stat_sent;
    s1 = *ngx_stat_1xx;
    s2 = *ngx_stat_2xx;
    s3 = *ngx_stat_3xx;
    s4 = *ngx_stat_4xx;
    s5 = *ngx_stat_5xx;
    reqs = *ngx_stat_requests;
    ac = *ngx_stat_accepted;
    rt = *ngx_stat_request_time;
    rd = *ngx_stat_reading;
    wr = *ngx_stat_writing;
    wt = *ngx_stat_waiting;
    qps = ((ctx->peroid->requests - ctx->peroid->last_requests) * 1000) / tf;
    in_bw = ((ctx->peroid->received - ctx->peroid->last_received) * 1000) / tf;
    out_bw = ((ctx->peroid->sent - ctx->peroid->last_sent) * 1000) / tf;

    buf = ngx_http_traffic_get_buf(r->pool, &chain);
    if(buf == NULL) {
        goto error;
    }

    buf->last = ngx_sprintf(buf->last, "{\n"
            "  \"nginx\": {\n"
            "    \"received\": %ul,\n"
            "    \"sent\": %ul,\n"
            "    \"requests\": %ul,\n"
            "    \"connections\": %ul,\n"
            "    \"qps\": %ul,\n"
            "    \"reading\": %ul,\n"
            "    \"writing\": %ul,\n"
            "    \"waiting\": %ul,\n"
            "    \"rt\": %ul,\n"
            "    \"in_bw\": %ul,\n"
            "    \"out_bw\": %ul,\n"
            "    \"1xx\": %ul,\n"
            "    \"2xx\": %ul,\n"
            "    \"3xx\": %ul,\n"
            "    \"4xx\": %ul,\n"
            "    \"5xx\": %ul\n"
            "  },\n",
            sr, ss, reqs, ac, qps, rd, wr, wt, rt, in_bw,
            out_bw, s1, s2, s3, s4, s5);

    ngx_shmtx_lock(ngx_http_traffic_ctx->mutex);

    buf->last = ngx_sprintf(buf->last, "  \"servers\": [\n");

    tscfp = ctx->servers.elts;
    for(i = 0; i < ctx->servers.nelts; ++i) {
        tscf = tscfp[i];

        buf = ngx_http_traffic_get_buf(r->pool, &chain);
        if(buf == NULL) {
            goto error;
        }

        /* server start */
        buf->last = ngx_sprintf(buf->last, "    {\n"
                                        "      \"name\": \"%V\",\n"
                                        "      \"received\": %ul,\n"
                                        "      \"sent\": %ul,\n"
                                        "      \"requests\": %ul,\n"
                                        "      \"1xx\": %ul,\n"
                                        "      \"2xx\": %ul,\n"
                                        "      \"3xx\": %ul,\n"
                                        "      \"4xx\": %ul,\n"
                                        "      \"5xx\": %ul,\n"
                                        "      \"locations\": [\n",
                                tscf->name,
                                tscf->elt->record.received,
                                tscf->elt->record.sent,
                                tscf->elt->record.requests,
                                tscf->elt->record.status_1xx,
                                tscf->elt->record.status_2xx,
                                tscf->elt->record.status_3xx,
                                tscf->elt->record.status_4xx,
                                tscf->elt->record.status_5xx);

        /* location start */
        tlcfp = tscf->locations.elts;
        for(j = 0; j < tscf->locations.nelts; ++j) {
            tlcf = tlcfp[j];

            buf = ngx_http_traffic_get_buf(r->pool, &chain);
            if(buf == NULL) {
                goto error;
            }

            buf->last = ngx_sprintf(buf->last, "        {\n"
                                            "          \"name\": \"%V\",\n"
                                            "          \"received\": %ul,\n"
                                            "          \"sent\": %ul,\n"
                                            "          \"requests\": %ul\n"
                                            "        }",
                                    tlcf->name,
                                    tlcf->elt->record.received,
                                    tlcf->elt->record.sent,
                                    tlcf->elt->record.requests);

            if(tscf->locations.nelts - j != 1) {
                buf->last = ngx_sprintf(buf->last, ",\n");
            }
        }

        /* location end*/
        buf->last = ngx_sprintf(buf->last, "\n      ]\n    }");

        if(ctx->servers.nelts - i != 1) {
            buf->last = ngx_sprintf(buf->last, ",\n");
        }
    }
    buf->last = ngx_sprintf(buf->last, "\n  ],\n");
    /* server end */


    /* upstream start */
    buf->last = ngx_sprintf(buf->last, "  \"upstreams\": [\n");

    uscfp = umcf->upstreams.elts;
    for(i = 0; i < umcf->upstreams.nelts; ++i) {
        uscf = uscfp[i];
        tscf = uscf->srv_conf[ngx_http_traffic_module.ctx_index];

        buf = ngx_http_traffic_get_buf(r->pool, &chain);
        if(buf == NULL) {
            goto error;
        }

        buf->last = ngx_sprintf(buf->last, "    {\n"
                                        "      \"name\": \"%V\",\n"
                                        "      \"received\": %ul,\n"
                                        "      \"sent\": %ul,\n"
                                        "      \"requests\": %ul,\n"
                                        "      \"1xx\": %ul,\n"
                                        "      \"2xx\": %ul,\n"
                                        "      \"3xx\": %ul,\n"
                                        "      \"4xx\": %ul,\n"
                                        "      \"5xx\": %ul,\n"
                                        "      \"peers\": [\n",
                                tscf->name,
                                tscf->elt->record.received,
                                tscf->elt->record.sent,
                                tscf->elt->record.requests,
                                tscf->elt->record.status_1xx,
                                tscf->elt->record.status_2xx,
                                tscf->elt->record.status_3xx,
                                tscf->elt->record.status_4xx,
                                tscf->elt->record.status_5xx);

        /* peers */
        for(j = 0; j < tscf->locations.nelts; ++j) {
            tlcf = ((ngx_http_traffic_loc_conf_t *)tscf->locations.elts) + j;

            buf = ngx_http_traffic_get_buf(r->pool, &chain);
            if(buf == NULL) {
                goto error;
            }

            buf->last = ngx_sprintf(buf->last, "        {\n"
                                            "           \"name\": \"%V\",\n"
                                            "           \"received\": %ul,\n"
                                            "           \"sent\": %ul,\n"
                                            "           \"requests\": %ul,\n"
                                            "           \"1xx\": %ul,\n"
                                            "           \"2xx\": %ul,\n"
                                            "           \"3xx\": %ul,\n"
                                            "           \"4xx\": %ul,\n"
                                            "           \"5xx\": %ul\n"
                                            "        }",
                                    tlcf->name,
                                    tlcf->elt->record.received,
                                    tlcf->elt->record.sent,
                                    tlcf->elt->record.requests,
                                    tlcf->elt->record.status_1xx,
                                    tlcf->elt->record.status_2xx,
                                    tlcf->elt->record.status_3xx,
                                    tlcf->elt->record.status_4xx,
                                    tlcf->elt->record.status_5xx);

            if(tscf->locations.nelts - j != 1) {
                buf->last = ngx_sprintf(buf->last, ",");
            }
        }

        buf->last = ngx_sprintf(buf->last, "\n      ]\n    }");
    }

    buf->last = ngx_sprintf(buf->last, "\n  ]\n");
    /* upstream end */

    buf->last = ngx_sprintf(buf->last, "}\n");

    ngx_shmtx_unlock(ngx_http_traffic_ctx->mutex);

    for(chain = &out; chain; chain = chain->next) {
        buf = chain->buf;

        r->headers_out.content_length_n += buf->last - buf->pos;
    }

    ngx_str_set(&r->headers_out.content_type, "application/json");
    r->headers_out.status = NGX_HTTP_OK;

    buf->last_buf = 1;

    rc = ngx_http_send_header(r);
    if(rc != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    return ngx_http_output_filter(r, &out);
error:
    ngx_shmtx_unlock(ctx->mutex);

    return NGX_HTTP_INTERNAL_SERVER_ERROR;
}


ngx_int_t
ngx_http_traffic_update_upstream(ngx_http_upstream_srv_conf_t *uscf)
{
    u_char                                  buf[256];
    ngx_uint_t                              i, j;
    ngx_http_traffic_ctx_t                  *ctx;
    ngx_http_traffic_hash_elt_t             *elt;
    ngx_http_traffic_srv_conf_t             *tscf;
    ngx_http_traffic_loc_conf_t             *tlcf;
    ngx_http_upstream_rr_peers_t            *peers;
    ngx_http_upstream_rr_peer_t             *peer;
    ngx_http_traffic_loc_conf_t             tlcfs[8];

    ctx = ngx_http_traffic_ctx;
    tscf = uscf->srv_conf[ngx_http_traffic_module.ctx_index];

    if(ctx == NULL) {
        /* module is not enabled */
        return NGX_OK;
    }

    ngx_memzero(tlcfs, sizeof(ngx_http_traffic_loc_conf_t) * 8);
    ngx_memcpy(buf, uscf->host.data, uscf->host.len);

    ngx_shmtx_lock(ctx->mutex);

    peers = uscf->peer.data;
    for(i = 0; i < peers->number;) {
        peer = peers->peer + i;

        for(j = 0; j < tscf->locations.nelts; ++j) {
            tlcf = ((ngx_http_traffic_loc_conf_t *) tscf->locations.elts) + j;

            if(peer->name.len != tlcf->name->len) {
                continue;
            }

            if(ngx_strncmp(peer->name.data, tlcf->name->data, tlcf->name->len) != 0) {
                continue;
            }

            ngx_memcpy(&tlcfs[i], tlcf, sizeof(ngx_http_traffic_loc_conf_t));
            goto next;
        }

        /* not found */
        elt = ngx_http_traffic_hash_get_free_elt(ctx->hash,
                                                 peer->name.len + tscf->name->len);
        if(elt == NULL) {
            goto error;
        }

        ngx_memcpy(elt->name, tscf->name->data, tscf->name->len);
        ngx_memcpy(elt->name + tscf->name->len, peer->name.data, peer->name.len);

        elt->srv_len = (uint32_t) tscf->name->len;
        elt->loc_len = (uint32_t) peer->name.len;
        tlcfs[i].elt = elt;
        tlcfs[i].name = &peer->name;

        ngx_http_traffic_hash_insert(ctx->hash, elt);
    next:
        i += 1;
    }

    for(i = 0; i < tscf->locations.nelts;) {
        tlcf = ((ngx_http_traffic_loc_conf_t *)tscf->locations.elts) + i;

        for(j = 0; j < peers->number; ++j) {
            peer = peers->peer + j;

            if(peer->name.len != tlcf->name->len) {
                continue;
            }

            if(ngx_strncmp(peer->name.data, tlcf->name->data, tlcf->name->len) != 0) {
                continue;
            }

            goto found;
        }

        /* not found */
        ngx_http_traffic_hash_delete(ctx->hash, tlcf->elt);

    found:
        i += 1;
    }

    ngx_shmtx_unlock(ctx->mutex);

    tscf->locations.nelts = peers->number;
    ngx_memcpy(tscf->locations.elts, tlcfs,
               sizeof(ngx_http_traffic_loc_conf_t) * peers->number);

    ngx_log_error_core(NGX_LOG_NOTICE, ngx_cycle->log, 0,
                        "ngx_http_traffic_module update peers for \"%V\"", tscf->name);

    return NGX_OK;

error:
    ngx_shmtx_unlock(ctx->mutex);

    return NGX_ERROR;
}
